using System;
using System.Collections.Generic;
using DevExpress.CodeRush.Core;
using DevExpress.CodeRush.StructuralParser;

namespace CR.StyleCop.Naming {

    /* TODO (fen):
     * A violation of this rule occurs when the names of certain types of elements do not begin with an upper-case letter. 
     * The following types of elements should use an upper-case letter as the first letter of the element name: namespaces, 
     * classes, enums, structs, delegates, events, methods, and properties.
     * In addition, any field which is public, internal, or marked with the const attribute should begin with an upper-case letter. 
     * Non-private readonly fields must also be named using an upper-case letter. 
     * If the field or variable name is intended to match the name of an item associated with Win32 or COM, 
     * and thus needs to begin with a lower-case letter, place the field or variable within a special NativeMethods class. 
     * A NativeMethods class is any class which contains a name ending in NativeMethods, and is intended as a placeholder for Win32 or COM wrappers. 
     * StyleCop will ignore this violation if the item is placed within a NativeMethods class.
     */

    public class ElementMustBeginWithUpperCaseLetterSearcher : BaseCodeIssueSearcher {

        public override void CheckCodeIssues(CheckCodeIssuesEventArgs ea) {
            var issueElements = GetIssueElements(ea.Scope);
            foreach (IElement element in issueElements) {
                ea.AddHint(element.FirstNameRange, "The name of a element does not begin with an upper-case letter.");
            }
        }

        private IElementCollection GetIssueElements(IElement scope) {
            var elements = new IElementCollection();
            if (scope == null)
                return elements;

            elements.AddRange(GetElements<INamespaceElement>(SourceModelUtils.GetElementEnumerator(scope, new ElementTypeFilter(typeof(INamespaceElement)))));
            elements.AddRange(GetElements<IClassElement>(SourceModelUtils.GetElementEnumerator(scope, new ElementTypeFilter(typeof(IClassElement)))));
            elements.AddRange(GetElements<IEnumerationElement>(SourceModelUtils.GetElementEnumerator(scope, new ElementTypeFilter(typeof(IEnumerationElement)))));
            elements.AddRange(GetElements<IStructElement>(SourceModelUtils.GetElementEnumerator(scope, new ElementTypeFilter(typeof(IStructElement)))));
            elements.AddRange(GetElements<IDelegateElement>(SourceModelUtils.GetElementEnumerator(scope, new ElementTypeFilter(typeof(IDelegateElement)))));
            elements.AddRange(GetElements<IEventElement>(SourceModelUtils.GetElementEnumerator(scope, new ElementTypeFilter(typeof(IEventElement)))));
            elements.AddRange(GetElements<IMethodElement>(SourceModelUtils.GetElementEnumerator(scope, new ElementTypeFilter(typeof(IMethodElement)))));
            elements.AddRange(GetElements<IPropertyElement>(SourceModelUtils.GetElementEnumerator(scope, new ElementTypeFilter(typeof(IPropertyElement)))));

            return elements;
        }

        private IElementCollection GetElements<T>(IEnumerable<IElement> elements) where T : class, IElement {
            var result = new IElementCollection();
            foreach (var item in elements) {
                var element = item as T;
                if (element == null)
                    continue;
                if (element is IMethodElement && (element.Name.StartsWith("get_") || element.Name.StartsWith("set_")))
                    continue;

                if (char.IsLower(element.Name[0]))
                    result.Add(element);
            }
            return result;
        }

    }
}
